# 导入需要的库
import sounddevice as sd  # 用于实时录音和音频流处理
import soundfile as sf    # 用于保存音频为 WAV 文件
import numpy as np        # 用于计算音频信号的 RMS 值（均方根）
import queue              # 提供线程安全的队列，用于在回调函数和主线程间传递数据
import time               # 用于控制循环等待时间，降低 CPU 占用

# 参数设置（这些是程序的核心配置，集中放在开头方便调整）
MIC_ID = 14               # 麦克风设备 ID，需根据你的设备调整（可用 sd.query_devices() 查看）
SAMPLERATE = 48000        # 采样率（Hz），表示每秒采集 48000 个音频样本
FILENAME = "output.wav"   # 保存录音的文件名，输出为 WAV 格式
THRESHOLD = 0.0008          # 声音检测的 RMS 阈值，决定什么强度的声音算“有效”（可根据环境调整）
SILENCE_DURATION = 0.75   # 无声持续时间（秒），连续 1 秒无声后停止录音
BLOCK_DURATION = 0.1      # 每次处理音频块的时长（秒），0.1 秒一个块，保证实时性
CHANNELS = 1              # 录音声道数，1 表示单声道

# 初始化全局变量（用于在回调函数和主线程间共享状态）
recording_queue = queue.Queue()  # 创建一个线程安全的队列，用于存储录音数据
is_recording = False            # 布尔值，表示当前是否正在录音（False 表示未录音）
silent_time = 0.0               # 记录连续无声的时间（秒），用于判断是否停止录音

# 定义音频回调函数，每次采集到音频数据时由 sounddevice 调用
def audio_callback(indata, frames, time_info, status):
    global is_recording, silent_time  # 声明使用全局变量，允许修改 is_recording 和 silent_time

    if status:  # 如果音频流有错误状态（如溢出），打印错误信息
        print(status)

    # 计算当前音频块的 RMS 值（均方根），表示音频强度
    rms = np.sqrt(np.mean(indata**2))  # indata 是音频数据数组，平方后求均值再开平方根

    # 判断是否有有效声音（RMS 高于阈值表示有声音）
    if rms > THRESHOLD:  # 如果 RMS 超过阈值，说明检测到声音
        is_recording = True  # 设置为录音状态
        silent_time = 0.0    # 重置无声时间计数，因为有声音了
    elif is_recording:       # 如果当前正在录音（is_recording 为 True）
        silent_time += BLOCK_DURATION  # 无声时间累加一个块的时长（0.1 秒）
        if silent_time >= SILENCE_DURATION:  # 如果无声时间达到 1 秒
            is_recording = False  # 停止录音

    # 如果正在录音，将音频数据存入队列
    if is_recording:  # 只有在录音状态下才保存数据
        recording_queue.put(indata.copy())  # 将音频数据拷贝后放入队列，防止数据被覆盖

# 定义主录音函数，负责启动录音、管理数据和保存文件
def start_recording(filepath):
    # 打印提示信息，告诉用户录音开始和触发条件
    print(f"使用设备 ID={MIC_ID} 开始实时录音...")
    print(f"当声音 RMS > {THRESHOLD} 时录音，{SILENCE_DURATION} 秒无声后停止。")

    # 创建一个空列表，用于存储从队列中取出的所有音频数据
    recorded_data = []

    # 创建音频输入流，配置采样率、设备、声道等参数
    with sd.InputStream(samplerate=SAMPLERATE, device=MIC_ID, channels=CHANNELS,
                        dtype='float32', blocksize=int(SAMPLERATE * BLOCK_DURATION),
                        callback=audio_callback):
        # samplerate：设置采样率（48000 Hz）
        # device：指定麦克风设备 ID
        # channels：设置单声道
        # dtype：音频数据类型为 32 位浮点数
        # blocksize：每个音频块的样本数（0.1 秒 * 48000 = 4800 样本）
        # callback：指定回调函数为 audio_callback

        try:  # 使用 try-except 捕获用户手动停止（Ctrl+C）等异常
            while True:  # 进入主循环，持续检查队列和录音状态
                try:
                    # 尝试从队列中获取音频数据，超时 0.1 秒
                    data = recording_queue.get(timeout=0.1)
                    recorded_data.append(data)  # 将获取的数据添加到 recorded_data 列表
                except queue.Empty:  # 如果队列为空（没有新数据）
                    # 检查是否满足停止条件：不在录音、无声 1 秒、且有录音数据
                    if not is_recording and silent_time >= SILENCE_DURATION and recorded_data:
                        print("检测到 0.75 秒无声")
                        break
                       #结束录音
                time.sleep(0.01)  # 短暂休眠 0.01 秒，降低 CPU 占用
        except KeyboardInterrupt:  # 捕获用户按 Ctrl+C 手动停止
            print("\n手动停止录音...")

    # 保存录音数据
    if len(recorded_data) > 8 :  # 如果 recorded_data 不为空（录到了音频）
        # 将所有音频块拼接成一个完整的数组
        recording = np.concatenate(recorded_data, axis=0)
        # 保存为 WAV 文件，指定文件名和采样率
        sf.write(filepath, recording, SAMPLERATE)
        print(f"录音已保存为 {filepath}")  # 提示保存成功
    else:  # 如果没有录到数据
        print("没有录制到有效音频，未保存文件。")  # 提示未保存

# 程序入口，只有直接运行脚本时才会执行
if __name__ == "__main__":
    start_recording("./tmp/1.wav")  # 调用主录音函数，启动程序